/**
 * 表达式计算
 */
import { isNumber } from 'utils'

// const N = Math.pow(10, 6) // 计算过程放大的倍数，解决浮点数精度问题

const runScript = (script, context) => {
  const func = new Function(script)

  try {
    return func.call(context)
  } catch (e) {
    console.error('表达式计算错误', script, context)
    return ''
  }
}

const makeScript = (statement, metas) => {
  const sorted = (metas || []).map(item => item.name).sort((a, b) => b.length - a.length)
  sorted.forEach(name => {
    statement = statement.replace(new RegExp(name, 'g'), `parseNumber(this["${name}"])`)
  })

  return `
    ${statement}
  `
}

export const getRuleDef = (rule) => {
  if (!rule) {
    return ''
  }

  let code = `
    function isNumber(val) {
      return !isNaN(Number(val))
    }

    function parseNumber(val) {
      if (val === undefined) {
        return 0
      }
      // 兼容如： "1,000.00"类数据
      if(typeof val === "string") val = val.replace(',', '')

      if (isNumber(val)) {
        return +val;
      }
      return val;
    }

  `

  if (rule.startsWith('CALC(')) {
    return code + 'return ' + rule.substring(5, rule.length - 1)
  } else if (rule.startsWith('SUM(')) {
    return code + 'return ' + rule.substring(4, rule.length - 1).split(',').join('+')
  } else if (rule.startsWith('AVERAGE(')) {
    const items = rule.substring(8, rule.length - 1).split(',')
    return code + `return (${items.join('+')}) / ${items.length}`
  } else if (rule.startsWith('VALUE(')) {
    const item = rule.substring(6, rule.length - 1)
    if (item.indexOf('.') === -1) {
      return code + 'return ' + item
    } else {
      const [parent, child] = item.split('.')
      return code + `
        var items = ${parent}, total = 0, len = items.length;
        for (var i = 0; i < len; i++) {
          total += parseNumber(items[i]['${child}']);
        }
        return total;
      `
    }
  }
}

export const recalculate = (data, metas) => {
  if (!metas || !data) {
    return data
  }

  const newData = { ...data }
  const metaList = [...metas].sort((a, b) => b.ref ? 1 : -1)
  metaList.forEach(meta => {
    if (meta.ref && meta.ref.length > 0) {
      newData[meta.name] = (newData[meta.name] || []).map(itemData => {
        return recalculate(itemData, meta.ref)
      })
    }

    if (meta.computeRule) {
      const ruleDef = getRuleDef(meta.computeRule)
      if (!ruleDef) {
        console.warn('invalid caculator rule', meta.computeRule)
        return
      }

      const runCode = makeScript(ruleDef, metas)
      let result = runScript(runCode, newData)
      if (meta.dataType === 'int') {
        result = parseInt(result)
      } else if (meta.dataType === 'double') {
        result = parseFloat(result).toFixed(2)
      } else if (isNumber(result)) {
        result = Number(result)
      }

      newData[meta.name] = result
    }
  })

  return newData
}
